//---------------------------------------------------------------------------
#include "stdafx.h"
//---------------------------------------------------------------------------
#include "AdvancedGraphicsViewer.h"
#include <QtGui/qevent.h>
#include "GeoXOutput.h"
#include <QtGui/qcursor.h>
#include <ctime>
#include "Random.h"

const int G_GLOSSY_NUM_RAYS = 50;
const int G_SOFTSHADOW_NUM_RAYS = 1;
const int G_REFRACTION_NUM_RAYS = 10;

bool G_GLOSSY_REFRACTIONS = false;

const bool G_SPHERE_LIGHTSOURCE = true;
//const Vector3f G_SPHERE_ORIGIN = makeVector3f(0.f, 40.f, 0.f);
//const float G_SPHERE_RADIUS = 20.f;
const Vector3f G_SPHERE_ORIGIN = makeVector3f(4.f, 20.f, -5.f);
const float G_SPHERE_RADIUS = 10.f;

static const Vector3f G_SUN_DIR = makeVector3f(0.57735f, 0.57735f, 0.57735f); // normalized + diagonal irlt checkerboard

GFXWidget3D::GFXWidget3D(QWidget *parent, const QGLWidget *shareWidget, Qt::WindowFlags f)
: QGLWidget(parent, shareWidget, f), view(&scene)
{
	m_per_vertex_normals = true;

	m_next_event = false;

	// SET MATERIAL PROPERTIES
	//floor
	m_materials[0].colorize = false;
	//m_materials[0].reflection_component = 1.0f;

	//sphere (1)
	m_materials[1].color = makeVector3f(.2f, .3f, .9f);
	m_materials[1].emiting = G_SPHERE_LIGHTSOURCE;
	m_materials[1].brightness = 1.f;

	//model1
	m_materials[2].color = makeVector3f(0.78f, 0.66f, .39f);

	//model2
	m_materials[3].color = makeVector3f(.9f, .3f, .3f);

	//model3
	m_materials[4].color = makeVector3f(.3f, .9f, .3f);

	//model4
	m_materials[5].color = makeVector3f(.8f, .8f, .8f);
	//m_materials[5].reflection_component = 0.7f;
	//m_materials[5].reflection_glossiness = 0.5f;
	
	//model5
	m_materials[6].color = makeVector3f(.9, .9f, .9f);

	//model6
	m_materials[7].color = makeVector3f(.9, .9f, .9f);
	
	m_materials[8].color = makeVector3f(.9, .9f, .9f);

	m_enable_shadows = false;
	m_enable_reflection = false;
	m_path_tracing = true;
	
	scene.addItem(&item);
	m_specular_intensity = 12;
	m_lightcolor = makeVector3f(1.f, 1.f, 1.f);
	m_wireframe = false;
	m_glossy_reflections = true;
	
	m_enable_dof = false;
	m_focal_length = 3.5f;
	m_aperture = 0.02;

	m_intensity_factor = 1.0f;
	m_radiance = NULL;

	initializeGL();
}

GFXWidget3D::~GFXWidget3D() {
	
}

void GFXWidget3D::createAndShowIMG(float intensity_factor) {
	m_intensity_factor = intensity_factor;

	if (m_radiance == NULL) {
		return;
	}
	else {
		int w = width();
		int h = height();
		img = QImage( w, h,  QImage::Format_RGB32);
		QRgb qbg = qRgb(101, 156, 239);
		QRgb black = qRgb(0, 0, 0);

#pragma omp parallel for
		for (int x = 0; x<w; x++) {
			for (int y=0; y<h; y++) {
				Vector3f res_color = m_radiance[x * h + y]  * m_intensity_factor;
				QRgb col = qRgb(res_color[0] * 255.f, res_color[1] * 255.f, res_color[2] * 255.f);
				img.setPixel(x, y, col);
			}
		}

	

		time_t rawtime;
		struct tm * timeinfo;
		char buffer[80];

		time (&rawtime);
		timeinfo = localtime(&rawtime);

		strftime(buffer,80,"%d-%m-%Y   %I_%M_%S",timeinfo);
		std::string str = "render ";
		str.append(buffer);
		str.append(".png");
		img.save(str.c_str());

		item.setPixmap( QPixmap::fromImage(img) );
		view.show();
	}
}

void GFXWidget3D::addModel(Model* newModel) {
	//makeCurrent();

	static VBOAttribute attr[] = { VBOAttribute(0, 3, GL_FLOAT),  VBOAttribute(1, 3, GL_FLOAT) };

	newModel->m_vertices.create();
	//newModel->m_vertices.bind();
	newModel->m_vertices.setAttributes(attr);
	newModel->m_vertices.bufferData( (GLuint) newModel->vertices.size() * sizeof(VertexPositionNormal), newModel->vertices.data() );
	
	newModel->m_indices.create();
	//newModel->m_indices.bind();
	newModel->m_indices.bufferData( newModel->indices.size() * sizeof(unsigned int), newModel->indices.data() );
	newModel->m_indices.setNumIndices( newModel->indices.size());
	newModel->m_indices.setIndexType(GL_UNSIGNED_INT);

	m_models.push_back(newModel);
	m_bvh.create(m_models);



	updateGL();
}

void GFXWidget3D::UpdateParams(float specular_intensity, bool enable_shadows, bool enable_reflection, bool enable_per_vertex_normals) {
	m_specular_intensity = specular_intensity;
	m_enable_shadows = enable_shadows;
	m_enable_reflection = enable_reflection;
	m_per_vertex_normals = enable_per_vertex_normals;
	
	updateGL();
}

void GFXWidget3D::setColor(Vector3f color)
{
	m_lightcolor = color;
	updateGL();
}

void GFXWidget3D::toggleWireframe() {
	m_wireframe = !m_wireframe;
	updateGL();
}

Vector3f GFXWidget3D::Calculate_triangle_normal(Vector3f v1, Vector3f v2 ,Vector3f v3)
{
	Vector3f s1 = v2-v1;
	Vector3f s2 = v3-v1;
	Vector3f normal = s1.crossProduct(s2);
	normal.normalize();
	return normal;
}

float gaussian_weight(float dx, float dy, float sigma) {
	return exp(-(dx*dx+dy*dy) / (2 * sigma * sigma));
}

Vector3f reflect(Vector3f I, Vector3f N) {
	return I - (N * 2.0f * (N*I));	
}

Vector3f refract(Vector3f I, Vector3f N, float n) {
	float cosI = -(N*I);
	float sinT2 = n * n * (1.0f - cosI * cosI);

	if (sinT2 > 1.0f) {
		return N;
	}

	float cosT = sqrt(1.0f - sinT2);
	return I * n + N * (n * cosI - cosT);
}

//static const Vector3f G_SUN_DIR = makeVector3f(0.0, 0.5, -0.5);

void GFXWidget3D::rayTransformDOF(Ray& ray) {
	Vector3f point_focalplane = ray.offset + ray.normal * m_focal_length;
	
	//Square lens?
	float u = rnd01() * 2.f - 1.f;
	float v = rnd01() * 2.f - 1.f;

	Vector3f cam_right = camera.getOrthoNormRightDirection() * m_aperture;//camera.getRotation() * makeVector3f(1, 0, 0);
	Vector3f cam_up = camera.getOrthoNormUpDirection() * m_aperture;//camera.getRotation() * makeVector3f(0, 1, 0);
	
	//cam_right.normalize();
	//cam_up.normalize();

	Vector3f offset_new = camera.getPosition() + (cam_right * u) + (cam_up * v);
	Vector3f dir_new = point_focalplane - offset_new;
	dir_new.normalize();
	ray.normal = dir_new;
	ray.offset = offset_new;
}

Vector3f RandomizeDirection( Vector3f dir, float amount) {
	float u = -0.5f + rnd01();
	float v = -0.5f + rnd01();
	Vector3f up = makeVector3f(0.f, 1.f, 0.f);
	Vector3f uu = up.crossProduct( dir);
	Vector3f vv = dir.crossProduct(uu);
	Vector3f p =  (uu * u  + vv * v) * amount + dir * 20.f;
	p.normalize();
	return p;
}

Vector3f GetUP( Vector3f dir) {

	if(dir[0] >= dir[1] && dir[0] >= dir[2])
   return makeVector3f(1.f, 0.f, 0.f);
  if(dir[1] >= dir[0] && dir[1] >= dir[2])
    return makeVector3f(0.f, 1.f, 0.f);
  if(dir[2] >= dir[0] && dir[2] >= dir[1])
    return makeVector3f(0.f, 0.f, 1.f);

  return makeVector3f(0.f, 1.f, 0.f);
}

Vector3f GetRandomDirectionToSphere(Vector3f point) {
	float angle = rnd01() * 2.f * M_PI;
	float len = rnd01() * G_SPHERE_RADIUS;

	Vector3f edge = G_SPHERE_ORIGIN - point;
	edge.normalize();
	Vector3f up = GetUP( -edge );
	Vector3f uu = up.crossProduct( edge );
	Vector3f vv = edge.crossProduct(uu);

	Vector3f light_p = G_SPHERE_ORIGIN + uu * cosf(angle) * len  + vv * sinf(angle) * len;
	Vector3f dir = light_p - point;
	dir.normalize();
	return dir;
	/*float x1 = 2.f*rnd01()-1.f;
	float x2 = 2.f*rnd01()-1.f;

	float x3 = sqrt(1.f - x1*x1 - x2*x2);

	Vector3f d = makeVector3f( 2.f * x1 * x3, 2.f * x2* x3, 1.f - 2.f*(x1*x1 + x2*x2));
	d.normalize();
	Vector3f light_p = G_SPHERE_ORIGIN + d * G_SPHERE_RADIUS;
	Vector3f dir = light_p - point;
	dir.normalize();*/
	/*float x1,x2,x3;
	do {
		x1 = 2.f*rnd01()-1.f;
		x2 = 2.f*rnd01()-1.f;
		x3 = x1*x1+x2*x2;
	} while (x3>1.f);

	float w1 = 1 - 2.f * x3;
	x3 = 2.f * sqrtf( 1.f - x3);
	float w2 = x1 * x3;
	float w3 = x2 * x3;
	Vector3f ww = makeVector3f(w1, w2, w3);

	Vector3f w = ww * G_SPHERE_RADIUS * rnd01() + G_SPHERE_ORIGIN - point;
	w.normalize();
	return w;*/
}

float GFXWidget3D::shadowShader(RaytraceResult& result) {
	if (G_SPHERE_LIGHTSOURCE && result.object_id == 1) return 1.f;

	float visibility = 1.f;
	Vector3f light_dir;
	if (G_SPHERE_LIGHTSOURCE) {
		light_dir = G_SPHERE_ORIGIN - result.location;
		light_dir.normalize();
	} else light_dir = G_SUN_DIR;

	for (int i=0; i<G_SOFTSHADOW_NUM_RAYS; i++) {
		Ray ray;
		ray.refraction_index = 1.0f;
		ray.normal = GetRandomDirectionToSphere( result.location );
		//ray.normal = RandomizeDirection(light_dir, 3.f);
		ray.offset = result.location;

		RaytraceResult shadow_result = raytrace(ray, 0.01f);

		if (G_SPHERE_LIGHTSOURCE)
		{
			if (!shadow_result.hit || shadow_result.object_id != 1) visibility -= 1.f / (float)(G_SOFTSHADOW_NUM_RAYS);
		} else {
			if (shadow_result.hit) visibility -= 1.f / (float)(G_SOFTSHADOW_NUM_RAYS);
		}
	}

	return visibility;
}



Vector3f GFXWidget3D::reflectShader(Ray& ray,RaytraceResult& result, float glossiness, int depth) {
	Vector3f reflect_dir = reflect( ray.normal, result.normal );



	if (glossiness > 0.0f) {
		Vector3f color = makeVector3f(0.f, 0.f, 0.f);

		for (int i=0; i<G_GLOSSY_NUM_RAYS; i++) {


			Ray reflect_ray;
			reflect_ray.normal = RandomizeDirection(reflect_dir, glossiness);
			reflect_ray.offset = result.location;
			reflect_ray.refraction_index = ray.refraction_index;
			reflect_ray.in_medium = ray.in_medium;

			RaytraceResult reflect_result = raytrace(reflect_ray, 0.01f, depth + 1);

			shade(reflect_ray, reflect_result, depth + 1);
			color += reflect_result.color / (float)(G_GLOSSY_NUM_RAYS);

		}

		return color;

	} else {
		Ray reflect_ray;
		reflect_ray.normal = reflect_dir;
		reflect_ray.offset = result.location;
		reflect_ray.refraction_index = ray.refraction_index;
		reflect_ray.in_medium = ray.in_medium;

		RaytraceResult reflect_result = raytrace(reflect_ray, 0.01f, depth + 1);

		shade(reflect_ray, reflect_result, depth + 1);

		return reflect_result.color;
	}

}

void GFXWidget3D::shade(Ray& ray, RaytraceResult& result, int depth) {
	if (depth > 3) return;

	if (result.hit) {

		float total_weight = 0.f;

		Material& mat = m_materials[result.object_id];
		if (mat.colorize) result.color = mat.color;

		float shadow = 1.0f, diffuse = 1.0f, specular = 0.0f;

		Vector3f reflect_col, refract_col;

		Vector2f v = phongShader(ray, result);
		diffuse = v[0];
		specular = v[1];

		if (m_enable_shadows && diffuse > 0.0f) {
			shadow = shadowShader(result);
		}

		// Phong shading
		total_weight +=  mat.ambient_component + mat.phong_component;
		result.color = result.color * mat.ambient_component  
		+ ( result.color + makeVector3f(specular,specular,specular)) * (mat.phong_component) * shadow * diffuse;

		if (m_enable_refraction && mat.refraction_component > 0.0f) {
			total_weight += mat.refraction_component;

			float refraction_index = ray.in_medium ? 1.0f : 1.5f;
			refract_col = refractionShader(ray, result, depth, refraction_index);
			result.color += refract_col * mat.refraction_component;
		}

		if ( m_enable_reflection && mat.reflection_component > 0.0f) {

			total_weight += mat.reflection_component;

			reflect_col = reflectShader(ray, result, mat.reflection_glossiness, depth);
			result.color += reflect_col * mat.reflection_component;

		}

		if (mat.emiting) {
			result.color += makeVector3f(mat.brightness, mat.brightness, mat.brightness);
		}

		result.color[0] = min(result.color[0] / total_weight, 1.0f);
		result.color[1] = min(result.color[1] / total_weight, 1.0f);
		result.color[2] = min(result.color[2] / total_weight, 1.0f);
	}
	else result.color = makeVector3f(101.f/255.f, 156.f/255.f, 239.f/255.f);
}

Vector3f GFXWidget3D::refractionShader(Ray& ray, RaytraceResult&result, int depth, float refraction_index)  {
	

	float n = ray.refraction_index / refraction_index;

	Vector3f dir = refract( ray.normal, result.normal, n );
	dir.normalize();

	//float c1 = result.normal * ray.normal;
	//Vector3f R1 = ray.normal + ( result.normal * 2.0f * c1 ) ;

	//float n1 = 1.0f;
	//float n2 = 1.5f;
	//if (ray.refraction_index == refraction_index) refraction_index = 1.0f;
	//float n = ray.refraction_index / refraction_index;
	//float c2 = sqrt( 1 - refraction_index * (1 - c1 * c1) );

	//Vector3f Rr = (ray.normal * n) + result.normal * (n * c1 - c2);

	//Ray refract_ray;
	//refract_ray.offset = result.location;
	//refract_ray.normal = Rr;
	//refract_ray.refraction_index = refraction_index;
	if (G_GLOSSY_REFRACTIONS) {
			Vector3f color = makeVector3f(0.f, 0.f, 0.f);

		for (int i=0; i<G_REFRACTION_NUM_RAYS; i++) {
			Ray refract_ray;
			refract_ray.in_medium = !ray.in_medium;
			refract_ray.normal = RandomizeDirection(dir, 0.6f);
			refract_ray.normal.normalize();
			refract_ray.refraction_index = refraction_index;
			refract_ray.offset = result.location;

			RaytraceResult result2 = raytrace( refract_ray, 0.01f);

			shade(refract_ray, result2, depth + 2);

			color += result2.color;
		}

		//result.color = result.color * 0.1f + result2.color * 0.9f;

		return color / float(G_REFRACTION_NUM_RAYS);
	} else {

		Ray refract_ray;
		refract_ray.in_medium = !ray.in_medium;
		refract_ray.normal = dir;
		refract_ray.normal.normalize();
		refract_ray.refraction_index = refraction_index;
		refract_ray.offset = result.location;

		RaytraceResult result2 = raytrace( refract_ray, 0.01f);

		shade(refract_ray, result2, depth + 2);

		return result2.color;
	}

	/*if (result2.hit) {
		if ( result2.object_id == 2) {
			result2.normal *= -1.0f;
			refractionShader(refract_ray, result2, depth + 1, ray.refraction_index);
			result.color = result2.color;
			return;
		} else {
			phongShader(refract_ray, result2);
			result.color = result2.color;
		}



		//
	} else {
		result.color = makeVector3f(101.f/255.f, 156.f/255.f, 239.f/255.f);
	}*/
}

Vector2f GFXWidget3D::phongShader(Ray& ray, RaytraceResult& result) {

	Vector3f light_dir;
	if (G_SPHERE_LIGHTSOURCE) {
		light_dir = G_SPHERE_ORIGIN - result.location;
		light_dir.normalize();
	} else light_dir = G_SUN_DIR;
	
	Vector3f reflection = normalize(-reflect(light_dir, result.normal));
	Vector3f eye_dir = ray.normal * -1.0f;

	float q = pow( max( reflection * eye_dir, 0.0f), m_specular_intensity);

	float diffuse = light_dir * result.normal;
	if (diffuse < 0.0f) diffuse = 0.0f;
	else if (diffuse > 1.0f) diffuse = 1.0f;

	if (diffuse == 0.0f) q = 0.0f;


	return makeVector2f(diffuse, q);
}

static int num_triangle_checks = 0, num_aabb_checks = 0;
void GFXWidget3D::raytraceRender(bool use_pathtracer, bool enable_refraction, bool use_bvh, AAMode aa) {
	m_enable_bvh = use_bvh;
	m_path_tracing = use_pathtracer;
	m_enable_refraction = enable_refraction;

	num_triangle_checks = 0;
	num_aabb_checks = 0;

	Vector3f origin = camera.getPosition();
	int w = width();
	int h = height();

	float fov = vf.getVerticalFieldOfView();
	float xToYAspectRatio = vf.getXToYAspectRatio();
	float z_near = vf.getZNearClip();

	float d = 1.0f/(float)tan(fov*0.5f*(M_PI/180.0f));

	float frustum_height = z_near / d;
	float frustum_width = z_near * xToYAspectRatio / d;

	float colmax = 0.f;
	
	if (m_radiance != NULL)
		delete [] m_radiance;
	
	m_radiance = new Vector3f[w*h];

	int nrays_per_pixel = 1;
	int k = 1;

	nrays_per_pixel = aa.nRaysPerPixel; //Ook meer pixels zonder AA
	if (aa.enabled) {
		//nrays_per_pixel = aa.nRaysPerPixel;
		if (aa.useDivideInSubPixel) {			
			k = (int)sqrtf(nrays_per_pixel); //Divide pixel in k x k subpixels where k is at most sqrt(nrays_per_pixel)
			if (k == 0) 
				k = 1; 
			nrays_per_pixel = k*k;			 //After determining k the total number of rays per pixel is set to k x k 
		}
	}

	output << "starting render @ "<< w << "x" << h << "x" << nrays_per_pixel << "\n";
	auto t = clock();

	#pragma omp parallel for
	for (int x = 0; x<w; x++) {
		for (int y=0; y<h; y++) {
			Vector3f res_color = makeVector3f(0,0,0);
			float weight_total = 0;
				
			for (int i=0; i < nrays_per_pixel; i++) {
				float xoff = 0, yoff = 0;
				float weight = 1;
				
				if (aa.enabled) {//Set pixel offsets
					if (aa.useDivideInSubPixel) { //Divide pixel in subpixels
						float subx = ((float)(i % k) + 1/(2*k)) / (float)k;
						float suby = ((float)(i / k) + 1/(2*k)) / (float)k;
						xoff = subx - 0.5;
						yoff = suby - 0.5;
						if (aa.useSubPixelJitter) { //jitter around subpixel origin
							xoff += (rnd01() - 0.5) / k;
							yoff += (rnd01() - 0.5) / k;
						}
					} else { //random sample in whole pixel area
						float spread = 2.0;
						if (aa.useGaussianWeighting)
							spread = 4.0;
						xoff = rnd01() * spread - (spread / 2); 
						yoff = rnd01() * spread - (spread / 2);
					}
					
					if (aa.useGaussianWeighting)
						weight = gaussian_weight(xoff, yoff, aa.gaussianWeightingSigma); //All rays are created equal. Some just count a little more than others.
				}

				float frac_x = 2.0f * (((float)x+xoff)/(float)w - 0.5f);
				float frac_y = 2.0f * (((float)y+yoff)/(float)h - 0.5f);
					
				Vector3f frustum_pos = makeVector3f( frac_x * frustum_width, -frac_y * frustum_height, -z_near );

				Ray ray;
				ray.normal = camera.getRotation() * frustum_pos;
				ray.normal.normalize();
				ray.offset = camera.getPosition();
				ray.in_medium = false;
				ray.refraction_index = 1.0f;

				if (m_enable_dof)
					rayTransformDOF(ray);

				RaytraceResult result;
				if (m_path_tracing) {
					result = pathtrace(ray);
					//result.color *= .2f;
				} else {
					result = raytrace(ray);

					shade(ray, result, 0);
				}
					
				res_color += result.color * weight;
				weight_total += weight;
			}

			res_color /= weight_total;

			if (res_color[0] > colmax)
				colmax = res_color[0];
			if (res_color[1] > colmax)
				colmax = res_color[1];
			if (res_color[2] > colmax)
				colmax = res_color[2];

			m_radiance[x * h + y] = res_color;

			
		}
	}

	t = clock() - t;

	createAndShowIMG(m_intensity_factor);
	
	//output << "Ray tracing done!\nRender saved as: ";
	//output << str;
	//output << "\n";
	output << "Num triangle checks: "<< num_triangle_checks << "\nNum AABB checks: "<< num_aabb_checks << "\n";
	output << "Calculation time: " << ((float)t)/CLOCKS_PER_SEC << " sec\n";
	
	//output << "> max = " << colmax << "\n";
}


RaytraceResult GFXWidget3D::raytrace_triangle( Ray& ray, VertexPositionNormal v0, VertexPositionNormal v1, VertexPositionNormal v2) {
	num_triangle_checks++;
	RaytraceResult result;
	result.hit = false;

	Vector3f edge1 = v1.position - v0.position;
	Vector3f edge2 = v2.position - v0.position;

	Vector3f pvec = ray.normal.crossProduct(edge2);
	float det = edge1 * pvec;

	if ( abs(det) <  0.000001) return result;

	float inv_det = 1.0f/det;
	Vector3f tvec = ray.offset - v0.position;
	float u = tvec * pvec * inv_det;
	if (u<0.0f || u>1.0f) return result;

	Vector3f qvec = tvec.crossProduct( edge1);
	float v = ray.normal * qvec * inv_det;
	if (v<0.0f || v+u>1.0f) return result;
	float t = (edge2 * qvec) * inv_det;

	/*Vector3f tvec = ray.offset - v0.position;
	float u = tvec * pvec;
	if ( u < 0.0 || u > det) return result;

	Vector3f qvec = tvec.crossProduct( edge1);
	float v = ray.normal * qvec;
	if (v < 0.0f || u + v > det) return result;

	float t = (edge2 * qvec)/det;
	u /= det;
	v /= det;*/

	result.fraction = t;
	result.hit = true;
	result.object_id = 2;

	result.location = ray.offset + ray.normal *t;

	if (m_per_vertex_normals) {
		result.normal =   v0.normal * (1.0f - u - v) +  v1.normal * u +  v2.normal * v;
		result.normal.normalize();
	} else {
		result.normal = Calculate_triangle_normal(v0.position, v1.position, v2.position); //per-triangle normals instead of the per-vertex + interpolation approach
	}
	
	result.color = makeVector3f(.0f, .0f, .0f); //Standard material color for triangles

	return result;
}

/*
 * courtesy of http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
 */
RaytraceResult raytrace_test_sphere(Ray& ray, float min_fraction, Vector3f origin, float radius) {
	RaytraceResult sphere_result;
	sphere_result.hit = false;
	sphere_result.object_id = 1;
	Vector3f sphere = origin; //makeVector3f(3, .5f, 3); //origin
	//float radius = 1;						// radius

	float a = ray.normal * ray.normal;
	float b = ((ray.offset - sphere) * 2) * ray.normal;
	float c = (ray.offset-sphere) * (ray.offset-sphere) - (radius * radius);
	float d = b * b - 4 * a * c;

	if ( d < 0) {
		sphere_result.hit = false;
	}
	else {
		float distSqrt = sqrtf(d);
		float q;
		if (b < 0)
			q = (-b - distSqrt)/2.0f;
		else
			q = (-b + distSqrt)/2.0f;

		float t0 = q / a;
		float t1 = c / q;
		float t;
		
		if (t0 > t1) {
			float temp = t0;
			t0 = t1;
			t1 = temp;
		}

		if (t1 < min_fraction)
			sphere_result.hit = false;
		else {
			sphere_result.hit = true;
			
			if (t0 < min_fraction)
				t = t1;
			else
				t = t0;

			//sphere_result.location = ray.offset + makeVector3f(ray.normal[0] * t, ray.normal[1] * t, ray.normal[2] * t);
			sphere_result.location = ray.offset + (ray.normal * t);
			sphere_result.normal = sphere_result.location - sphere;
			sphere_result.normal.normalize();
			sphere_result.fraction = t;
			sphere_result.color = makeVector3f(0.f, 0.f, 0.f);
		}
	}
	return sphere_result;
}

RaytraceResult GFXWidget3D::raytrace_floor(Ray& ray, float min_fraction) {
	float floor_height = -1.f;
	RaytraceResult floor_result;
	floor_result.hit = false;
	floor_result.fraction = FLT_MAX;
	floor_result.object_id = 0;

	if (abs(ray.normal[1]) > 0) { //Rays parallel to the y=0 plane will never intersect and cause div by 0 errors.
		float fraction = (-ray.offset[1] + floor_height) / ray.normal[1];
		if (fraction > min_fraction) { //Only use results in front of the camera
			floor_result.hit = true;
			floor_result.fraction = fraction;
			//floor_result.location = ray.offset + makeVector3f(fraction * ray.normal[0], fraction * ray.normal[1], fraction * ray.normal[2]); //float * Vector3f not defined???
			floor_result.location = ray.offset + ray.normal * fraction;
			if (ray.normal[1] < 0)
				floor_result.normal = makeVector3f(.0f, 1.f, .0f); // Floor normal points UP
			else
				floor_result.normal = makeVector3f(.0f, -1.f, .0f); // Floor normal points DOWN

			//determine color of checkerboard pattern (floored x+y coords are both even or both odd for white, else black)
			if (((int)floor(floor_result.location[0]) % 2 == 0 && (int)floor(floor_result.location[2]) % 2 == 0) ||
				((int)floor(floor_result.location[0]) % 2 != 0 && (int)floor(floor_result.location[2]) % 2 != 0)) 
					floor_result.color = makeVector3f(.98f, .98f, .98f);
			else floor_result.color = makeVector3f(.1f, .1f, .1f);
		}
	}

	return floor_result;
}

bool GFXWidget3D::raytrace_aabb( Ray& ray, AABB& bb, float min_fraction) {
	num_aabb_checks++;
	Vector3f T_1, T_2; 
    float t_near = -FLT_MAX; 
    float t_far = FLT_MAX;

    for (int i = 0; i < 3; i++){ 
		if (ray.normal[i] == 0.f){
			if ((ray.offset[i] < bb.min_corner[i]) || (ray.offset[i] > bb.max_corner[i])) return false; 
        } else {
			T_1[i] = (bb.min_corner[i] - ray.offset[i]) / ray.normal[i];
            T_2[i] = (bb.max_corner[i] - ray.offset[i]) / ray.normal[i];

            if(T_1[i] > T_2[i]){ 
                swap(T_1,T_2);
            }
            if (T_1[i] > t_near){
                t_near = T_1[i];
            }
            if (T_2[i] < t_far){
                t_far = T_2[i];
            }
            if( (t_near > t_far) || (t_far < 0.0f) ){
                return false;
            }
        }
    }

    return true;
}



RaytraceResult GFXWidget3D::bvhtrace(Ray& ray, BVHNode* node, float min_fraction) {
	if (node == NULL || !raytrace_aabb(ray, node->bounding_box, min_fraction)) {
		RaytraceResult no_result;
		no_result.hit = false;
		no_result.fraction = FLT_MAX;	
		return no_result;
	}

	if (node->left != NULL) {
		RaytraceResult left = bvhtrace(ray, node->left, min_fraction);
		RaytraceResult right = bvhtrace(ray, node->right, min_fraction);
		if (left.fraction < right.fraction) return left;
		else return right;
	}
	RaytraceResult end_result;
	end_result.hit = false;
	end_result.fraction = FLT_MAX;	

	for (int i=0; i < node->num_triangles; i++) {
		BVHTriangle& tr = node->triangles[i];
		VertexPositionNormal v0 = m_models[tr.object_id-2]->vertices.at( tr.indices[0] ); 
		VertexPositionNormal v1 = m_models[tr.object_id-2]->vertices.at( tr.indices[1] ); 
		VertexPositionNormal v2 = m_models[tr.object_id-2]->vertices.at( tr.indices[2] );
		RaytraceResult result = raytrace_triangle(ray, v0, v1, v2);
		if (result.hit && result.fraction > min_fraction && result.fraction < end_result.fraction) {
			end_result = result;
			end_result.object_id = tr.object_id;
		}
	}
	return end_result;
}


/*Vector3f SampleUpperHemisphereUniform(Vector3f N)
{
	float x, y, z;
	do 
	{
		x = 2.f*rnd01() - 1.f;
		y = 2.f*rnd01() - 1.f;
		z = 2.f*rnd01() - 1.f;
	} while(x*x + y*y + z*z > 1);
	////Vector3f sample = makeVector3f(1.f - 2.f * rnd01(), 1.f - 2.f * rnd01(), 1.f - 2.f * rnd01());
	Vector3f sample = makeVector3f(x, y, z);
	sample.normalize();

	/*float u = 2.f*rnd01() - 1.f;
	float theta = 2.f * M_PI * rnd01();
	
	float v = sqrtf(1 - (u*u));

	Vector3f sample = makeVector3f(cos(theta) * v, sin(theta) * v, u);
	sample.normalize();

	if (sample * N < 0) // Pointing in oposite direction?
		sample = -sample;

	return sample;
}




Vector3f LambertBRDF( RaytraceResult& result )
{
	return result.color / M_PI;
}

Vector3f LambertOutgoingVector( Vector3f& normal) {
	return SampleUpperHemisphereUniform(normal);
}

float LambertGetProbability( Vector3f& normal, Vector3f& out )
{ 
	return 1.0f/(2.0f*M_PI);
}

float LambertRussianRoulette(Vector3f brdf) {

	float p = max(max( brdf[0], brdf[1]), brdf[2]);
	return min(1.0f, p);
}

void GFXWidget3D::pathtraceLambert(RaytraceResult& result, int depth) {

	Vector3f rnd_dir = LambertOutgoingVector( result.normal );


	/*if (depth % 2 == 1)  {
		rnd_dir = G_SPHERE_ORIGIN - result.location;
		rnd_dir.normalize();


	Vector3f brdf = LambertBRDF( result);

	//float prob_russian_roulette = LambertRussianRoulette( brdf, result, rnd_dir );
	float prob_russian_roulette = LambertRussianRoulette(brdf);

	if (rnd01() < prob_russian_roulette) {
		result.color = makeVector3f(0.f, 0.f, 0.f);
		return;
	}

	float prob_rnd_dir = LambertGetProbability( result.normal, rnd_dir);

	Ray ray2;
	ray2.normal = rnd_dir;
	ray2.offset = result.location ;
	RaytraceResult result2 = pathtrace(ray2, depth +1, 0.01f);

	float cos_w = result.normal * rnd_dir;
	result.color = result2.color.componentProduct(brdf) * cos_w / prob_rnd_dir ;

	//if (false) { //shadow ray
	//	Ray ray3;
	//	ray3.normal = (G_SPHERE_ORIGIN - result.location);
	//	ray3.normal.normalize();
	//	ray3.offset = result.location;
	//	RaytraceResult result3 = raytrace(ray3, 0.01f);
	//	if (result3.hit && result3.object_id == 1) {
	//		cos_w = result.normal * ray3.normal;
	//		float prob_shadow_dir = LambertGetProbability( result.normal, ray3.normal);
	//		result.color = result.color * (1.f - prob_shadow_dir) + makeVector3f(1.f, 1.f, 1.f).componentProduct(brdf) * cos_w * prob_shadow_dir;
	//	}
	//}

	result.color /= ( 1 - prob_russian_roulette);
}

RaytraceResult GFXWidget3D::pathtrace(Ray& ray, int depth, float min_fraction) {
	
	RaytraceResult result = raytrace(ray, min_fraction);
	result.color = makeVector3f(0.f, 0.f, 0.f);


	if (!result.hit) {
		result.color = makeVector3f(0.f, 0.f, 0.f);
		return result;
	} else {

		if (result.object_id == 1) {
			result.color = makeVector3f(1.f, 1.f, 1.f);
			return result;
		}
		 result.color = m_materials[result.object_id].color;

		pathtraceLambert(result, depth);
		return result;

	}


}*/

Vector3f SampleUpperHemisphereUniform(Vector3f N)
{
	float u = 2.f*rnd01() - 1.f;
	float theta = 2.f * M_PI * rnd01();
	
	float v = sqrtf(1 - (u*u));

	Vector3f sample = makeVector3f(cos(theta) * v, sin(theta) * v, u);
	

	if (sample * N < 0) // Pointing in oposite direction?
		sample = -sample;

	sample.normalize();

	return sample;
}


Vector3f LambertBRDF( RaytraceResult& result )
{
	return result.color / M_PI;
}

Vector3f LambertOutgoingVector( Vector3f& normal) {
	return SampleUpperHemisphereUniform(normal);
}

float LambertGetProbability( Vector3f& normal, Vector3f& out )
{ 
	return 1.0f/(2.0f*M_PI);
}

float LambertRussianRoulette(Vector3f brdf) {

	float p = max(max( brdf[0], brdf[1]), brdf[2]);
	return min(1.0f, p);
}

void GFXWidget3D::pathtraceLambert(RaytraceResult& result, int depth) {

	Vector3f rnd_dir = LambertOutgoingVector( result.normal );

	float cos_w = result.normal * rnd_dir;
	Vector3f brdf = LambertBRDF( result);

	float prob_russian_roulette = LambertRussianRoulette( brdf );

	if (rnd01() < prob_russian_roulette) {
		result.color = makeVector3f(0.f, 0.f, 0.f);
		return;
	}

	float prob_rnd_dir = LambertGetProbability( result.normal, rnd_dir);

	Ray ray2;
	ray2.normal = rnd_dir;
	ray2.offset = result.location;
	RaytraceResult result2 = pathtrace(ray2, depth +1, 0.01f);

	
	result.color = result2.color.componentProduct(brdf) * cos_w / prob_rnd_dir ;

	result.color /= ( 1.f - prob_russian_roulette);
}

RaytraceResult GFXWidget3D::pathtrace(Ray& ray, int depth, float min_fraction) {
	
	RaytraceResult result = raytrace(ray, min_fraction);
	result.color = makeVector3f(0.f, 0.f, 0.f);


	if (!result.hit) {
		result.color = makeVector3f(0.f, 0.f, 0.f);
		return result;
	} else {

		if (result.object_id == 1) {
			result.color = makeVector3f(1.f, 1.f, 1.f);
			return result;
		}
		 result.color = m_materials[result.object_id].color;

		pathtraceLambert(result, depth);
		return result;

	}


}


RaytraceResult GFXWidget3D::raytrace(Ray& ray, float min_fraction, int depth) {

	RaytraceResult end_result = raytrace_floor(ray, min_fraction);
	
	//Added hard-coded Sphere for testing purposes (reflection)
	RaytraceResult sphere_result = raytrace_test_sphere(ray, min_fraction, G_SPHERE_ORIGIN, G_SPHERE_RADIUS);
	if (sphere_result.hit && sphere_result.fraction < end_result.fraction) 
		end_result = sphere_result;

	if (m_enable_bvh) {
		RaytraceResult result = bvhtrace( ray, m_bvh.getRoot(), min_fraction );
		if (result.hit && result.fraction > min_fraction && result.fraction < end_result.fraction) {
			end_result = result;
		}
	} else {
		for each (Model* m_model in m_models) {
			for (unsigned int i=0; i<m_model->indices.size(); ) {
				VertexPositionNormal v0 = m_model->vertices.at( m_model->indices.at(i++)); 
				VertexPositionNormal v1 = m_model->vertices.at( m_model->indices.at(i++)); 
				VertexPositionNormal v2 = m_model->vertices.at( m_model->indices.at(i++));

				RaytraceResult result = raytrace_triangle(ray, v0, v1, v2);
				if (result.hit && result.fraction > min_fraction && result.fraction < end_result.fraction) {
					end_result = result;
				}
			}
		}	
	}
	
	if (ray.in_medium) end_result.normal *= -1.f;

	return end_result;
}

void GFXWidget3D::initializeGL() 
{
	makeCurrent();

	GLenum err = glewInit();

	glViewport(0,0,width(),height());

	camera.setPosition(makeVector3f(0.f,0.0f,20.0f));

	m_center = makeVector3f(0.f, 2.f, 0.f);
	m_distance = 15.f;
	m_angle_x = 0.f;
	m_angle_y = 0.f;

	updateCam();

	/*Model* newModel = new Model();
	
	//float vert[] = {0.0f, 0.8f, -1.0f, -0.8f, -0.8f, -1.0f, 0.8f, -0.8f,  -1.0f }; 
	float vert[] = {  -1.0f,-1.0f,-1.0f,  0.f, 0.f, 1.f,
    -1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f,-1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
    -1.0f, 1.0f, 1.0f, 0.f, 0.f, 1.f,
	1.0f,-1.0f, 1.0f, 0.f, 0.f, 1.f };

	VertexPositionNormal* vpn = (VertexPositionNormal*) &vert[0];

	for (uint i=0; i<36; i++)
	newModel->vertices.push_back( vpn[i] );

	static VBOAttribute attr[] = { VBOAttribute(0, 3, GL_FLOAT),  VBOAttribute(1, 3, GL_FLOAT) }; //, VBOAttribute(1, 3, GL_FLOAT)

	GLuint indices[] = { 0, 1, 2,3 ,4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ,24, 25, 26,27,28,29,30,31,32,33,34,35 };

	for (uint i=0; i<36; i++)
	newModel->indices.push_back(indices[i]);

	addModel(newModel);
	m_vertices.create();
	m_vertices.setAttributes(attr);
	m_vertices.bufferData( vert);
	//
	//GLuint indices[] = { 0, 1, 2,3 ,4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ,24, 25, 26,27,28,29,30,31,32,33,34,35 };
	m_indices.create();
	m_indices.bufferIndices( indices );*/

	const char* vertex_shader = "#version 150 core \n\
uniform mat4 world_to_screen;\
in vec3 in_Position;\n\
in vec3 in_Normal;\n\
out vec3 ex_Normal; \n\
out vec3 ex_Pos; \n\
void main(void) \n\
{ \
      gl_Position = world_to_screen * vec4(in_Position, 1.0); \n\
	  ex_Pos = in_Position; \
	  ex_Normal = in_Normal; \n\
}";
//	  ex_Pos = (world_to_cam * vec4(in_Position, 1.0)).xyz; \n
		const char* fragment_shader = "#version 150 core \n\
uniform vec3 view_pos;\
uniform float specular_intensity; \
uniform vec3 light_color;\
uniform vec3 G_SUN_DIR; \
in vec3 ex_Normal; \n\
in vec3 ex_Pos; \n\
out vec4 out_Color; \n\
void main(void) \n\
{ \
vec3 l_dir = normalize( G_SUN_DIR);\
vec3 f_dir = normalize(ex_Normal);\
vec3 eye_dir = normalize( view_pos -ex_Pos); \
vec3 reflection = normalize(-reflect(l_dir, ex_Normal)); \
	  vec3 color = light_color * vec3(0.7, 0.7, 0.7);\
	  vec3 specular = vec3(pow(max(dot(reflection, eye_dir),0.0), specular_intensity)); \
	  float diffuse = 0.3 + 0.7 * clamp(dot(f_dir,l_dir), 0.0, 1.0); \
	  if (diffuse <= 0.3) specular *= 0.0;\
	  out_Color = vec4(specular, 0.0) +  diffuse *  vec4(color,1.0); \n\
}";

	static Shader::attribute attributes[] = { { "in_Position", 0 }, { "in_Normal", 1 } };

	m_vertex_shader.compile( vertex_shader );
	m_fragment_shader.compile( fragment_shader );

	m_shader_3d.setVertexShader(m_vertex_shader);
	m_shader_3d.setFragmentShader(m_fragment_shader);
	m_shader_3d.compile( attributes );

}

void GFXWidget3D::paintGL() {
	glClearColor(101.f / 255.f, 156.f / 255.f, 239.f/255.f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	

	Matrix4f world_matrix =	camera.getViewMatrix();

	float32 aspect;
	if (height() > 0) {
		aspect = ((float)width())/((float)height());
	} else {
		aspect = 1;
	}
	vf.setXToYAspectRatio(aspect);

	Matrix4f projection_matrix = vf.getProjectionMatrix();

	Matrix4f world_to_screen = projection_matrix * world_matrix;


	Vector3f col = camera.getPosition();
	Vector3f pos = makeVector3f(col[0], col[1], col[2]);

	m_shader_3d.bind();
	m_shader_3d.setVector3f("light_color", m_lightcolor);
	m_shader_3d.setVector3f("G_SUN_DIR", G_SUN_DIR);
	m_shader_3d.setVector3f("view_pos", pos);
	m_shader_3d.setMatrix4f("world_to_cam", world_matrix);
	m_shader_3d.setMatrix4f("world_to_screen", world_to_screen);
	m_shader_3d.setFloat("specular_intensity", m_specular_intensity);

	glPolygonMode(GL_FRONT_AND_BACK, m_wireframe ? GL_LINE : GL_FILL);
	glEnable(GL_DEPTH_TEST);
	//glEnable(GL_CULL_FACE);

	for (int i=0; i<m_models.size(); i++) {
		RenderBuffer( GL_TRIANGLES, m_models.at(i)->m_vertices, m_models.at(i)->m_indices );
	}
	
}


void GFXWidget3D::resizeGL(int width, int height) {
	glViewport(0,0,width,height);
}

void GFXWidget3D::mousePressEvent(QMouseEvent *event) {
	//m_mouse_x = event->x();
	//m_mouse_y = event->y();
	m_mouse_point = QCursor::pos();

	ShowCursor(false);

}

void GFXWidget3D::mouseReleaseEvent (QMouseEvent * event) {
	ShowCursor(true);
	QCursor::setPos( m_mouse_point );
}

void GFXWidget3D::updateCam() {
	Matrix3f rot = (makeRotX3f((float) m_angle_x).transpose()) * (makeRotY3f( (float) m_angle_y).transpose());
	Matrix3f rot_T = rot.transpose();



	Vector3f new_right = rot_T[0];
	Vector3f new_up = rot_T[1];
	Vector3f new_forward = rot_T[2];

	camera.setPosition( m_center +  new_forward * m_distance );
	camera.setOrthoNormRightDirection( new_right );
	camera.setOrthoNormUpDirection( new_up );
	camera.setOrthoNormViewDirection( new_forward );
}

void GFXWidget3D::mouseMoveEvent(QMouseEvent *event) {
	if(event->buttons() & Qt::RightButton)
	{	
		QPoint diff = QCursor::pos() - m_mouse_point;
		QCursor::setPos(m_mouse_point);
		//int diffy = event->y() - m_mouse_y;
		//m_mouse_x = event->x();
		//m_mouse_y = event->y();

		m_distance += (float) diff.y() / 10.f;

		updateCam();

		//QCursor::setPos( m_mouse_point );
		updateGL();
	} else if ( event->buttons() & Qt::LeftButton) {

		QPoint diff = QCursor::pos() - m_mouse_point;
		QCursor::setPos(m_mouse_point);
		//int diffx = event->x() - m_mouse_x;
		//int diffy = event->y() - m_mouse_y;
		//m_mouse_x = event->x();
		//m_mouse_y = event->y();

		m_angle_x -= diff.y() / 50.f;
		m_angle_y -= diff.x() / 50.f;

		updateCam();

		//QCursor::setPos( m_mouse_point );
		updateGL();

	} else if ( event->buttons() & Qt::MiddleButton) {
		QPoint diff = QCursor::pos() - m_mouse_point;
		QCursor::setPos(m_mouse_point);
		//int diffx = event->x() - m_mouse_x;
		//int diffy = event->y() - m_mouse_y;
		//m_mouse_x = event->x();
		//m_mouse_y = event->y();

		Vector3f up = camera.getOrthoNormUpDirection();
		Vector3f right = camera.getOrthoNormRightDirection();
		Vector3f delta = right * (float) diff.x() / 10.f + up * (float) diff.y() / -10.f;
		m_center += delta;

		updateCam();

		//QCursor::setPos( m_mouse_point );
		updateGL();
	}
}

#ifdef _WIN32
#include "moc/AdvancedGraphicsViewer_moc.cpp"
#else
#include "moc_GLGeometryViewer3D.cpp"
#endif